package example.gl2jniforceball;


import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.util.Log;
import android.view.KeyEvent;

class Box2dRenderer implements GLSurfaceView.Renderer {
    public static final long NANOSECONDS_PER_SECOND = 1000 * 1000 * 1000;
    public static final float SECONDS_PER_NANOSECOND = 1f / NANOSECONDS_PER_SECOND;

    private static final String mVertexShader = "uniform mat4 uMVPMatrix;\n"
            + "attribute vec4 aPosition;\n" + "attribute vec2 aTextureCoord;\n"
            + "varying vec2 vTextureCoord;\n" + "void main() {\n"
            + "  gl_Position = uMVPMatrix * aPosition;\n"
            + "  vTextureCoord = aTextureCoord;\n" + "}\n";

    private static final String mFragmentShader = "precision mediump float;\n"
            + "varying vec2 vTextureCoord;\n" + "uniform sampler2D sTexture;\n"
            + "void main() {\n"
            + "  gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + "}\n";

    private float[] mMVPMatrix = new float[16];
    private float[] mProjMatrix = new float[16];
    private float[] mMMatrix = new float[16];
    private float[] mVMatrix = new float[16];

    private int mProgram;
    private int mBallTextureID;
    private int mWallTextureID;
    private int mPlayerTextureID;
    private int muMVPMatrixHandle;
    private int maPositionHandle;
    private int maTextureHandle;

    private Context mContext;
    private static String TAG = "Box2dRenderer";
    
    private float mWindowW;
    private float mWindowH;
    private float mAspectRatio;


    /*****************************************************************/
    /*********************** Box2D related members ********************/
    /*****************************************************************/
    private final float world_maxX = 24f;
    private final float world_maxY = 36f;
    private final float world_minX = 0f;
    private final float world_minY = 0f;
    private final float world_width =  world_maxX - world_minX;
    private final float world_height = world_maxY - world_minY;
//    private final float world_half_width = world_width / 2f;
//    private final float world_half_height = world_height / 2f;
    
    private int gravityX = 0;
    private int gravityY = 0;
    private int numBalls = 1;
    private float ballRadius = 1f;
    private float blockWidth = 4f;        
    float wallW = 1.5f;
    float wallmW = wallW / 2f;
    int playerIndex;
    int firstIndex;
    int lastIndex;
    BodyInfo info = new BodyInfo();

    public class WallInfo {
        public float width;
        public float height;
        public int id;

        public WallInfo(int id, float width, float height) {
            this.id = id;
            this.width = width;
            this.height = height;            
        }
    }

    private WallInfo downWall, upWall, leftWall, rightWall;
    private WallInfo upLeftCorner, upRightCorner, downLeftCorner, downRightCorner;

    // Performance data
    protected float timeStep = 1.0f / 60f;
    protected int velocityIterations = 8;
    protected int positionIterations = 3;
    protected long lastStep = System.currentTimeMillis();    
    protected long current;
    protected int target = (int) (1000 / 60.0f);
    protected float frameAverage = target;
    protected long lastFrame = System.currentTimeMillis();
    protected int fps;
    
    protected long lastTick;

    /*****************************************************************/

    public Box2dRenderer(Context context) {
        mContext = context;
    }

    @Override
    public void onDrawFrame(GL10 glUnused) {
        // Ignore the passed-in GL10 interface, and use the GLES20
        // class's static methods instead.
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
        GLES20.glUseProgram(mProgram);
        checkGlError("glUseProgram");

        GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT,
                false, 0, OpenGLUtils.getBoxTriangleVertexBuffer());
        GLES20.glEnableVertexAttribArray(maPositionHandle);

        GLES20.glVertexAttribPointer(maTextureHandle, 2, GLES20.GL_FLOAT,
                false, 0, OpenGLUtils.getBoxTriangleTextureBuffer());
        GLES20.glEnableVertexAttribArray(maTextureHandle);

        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);

        current = System.currentTimeMillis();
        frameAverage = (frameAverage * 10 + (current - lastFrame)) / 11;
        lastFrame = current;
        fps = (int) (1000 / frameAverage);
        
        float d = ballRadius * 2;

        // draw balls
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mBallTextureID);
        for (int i = firstIndex; i <= lastIndex; i++)
            drawBody(i, d, d);
        
        // draw corner walls
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mWallTextureID);
        drawBody(upLeftCorner.id, upLeftCorner.width, upLeftCorner.height);
        drawBody(upRightCorner.id, upRightCorner.width, upRightCorner.height);
        drawBody(downLeftCorner.id, downLeftCorner.width, downLeftCorner.height);
        drawBody(downRightCorner.id, downRightCorner.width, downRightCorner.height);

        // draw walls
        drawBody(downWall.id, downWall.width, downWall.height);
        drawBody(upWall.id, upWall.width, upWall.height);
        drawBody(leftWall.id, leftWall.width, leftWall.height);
        drawBody(rightWall.id, rightWall.width, rightWall.height);
        
        // Draw player
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mPlayerTextureID);
        drawBody(playerIndex, blockWidth, blockWidth);
        
//        long nanosecondsElapsed = getNanosecondsElapsed();
//        final float secondsElapsed = nanosecondsElapsed * SECONDS_PER_NANOSECOND;        
//        lastTick += nanosecondsElapsed;
        
        JniInterface.step(timeStep, velocityIterations, positionIterations);

//        long before = System.currentTimeMillis();
//        // Log.i("app",""+before+","+(lastStep+target));
//        long beforeStep = before - current;
//        // System.out.println(current+","+(lastStep+target));
//        if (before > lastStep + target) {
//            // step
//
//            JniInterface.step(timeStep, velocityIterations, positionIterations);
//            long end = System.currentTimeMillis();
//            long endStep = end - before;
//            // Log.i("app","stepped:"+beforeStep+","+endStep);
//            lastStep = end;
//        } else {
//            // Log.i("app","-skipped");
//        }

    }

    private long getNanosecondsElapsed() {
        final long now = System.nanoTime();

        return now - lastTick;
    }
    
    private float[] mFloatValues = new float[2];
    private float[] screenToWorld(float x, float y){              
        mFloatValues[0] = x * world_width / mWindowW; 
        mFloatValues[1] = (mWindowH - y) * world_height / mWindowH;
//        mFloatValues[1] = (mWindowH - y + 110) * world_height / mWindowH; // for AVD emulator
        return mFloatValues;
    }
    //Use this for otrho(0,480,0,720);
    private static final float WORLD_SCALE = 20f;    
    private float worldToOpenGLMeasure(float m) {        
        return m * WORLD_SCALE;
    }    
//    //Use this for otrho(-ratio,ratio,-1,1);
//    private float[] worldToOpenGLMeasures(float w, float h) {        
//        mFloatValues[0] = w * mAspectRatio / world_half_width; 
//        mFloatValues[1] = h / world_half_height;
//        return mFloatValues;
//    }
//    private float[] worldToOpenGLCoords(float x, float y) {        
//        mFloatValues[0] = (x - world_half_width) * mAspectRatio / world_half_width;
//        mFloatValues[1] = (y - world_half_height) / world_half_height;
//        return mFloatValues;
//    }
    
    private void drawBody(int index, float width, float height) {
        JniInterface.getBodyInfo(info, index);

        //Use this for otrho(0,480,0,720);
        float x = worldToOpenGLMeasure (info.getX());
        float y = worldToOpenGLMeasure (info.getY());
        float w = worldToOpenGLMeasure (width) / 2;
        float h = worldToOpenGLMeasure (height) / 2;
        
//        //Use this for otrho(-ratio,ratio,-1,1);
//        float[] xy = worldToOpenGLCoords(info.getX(), info.getY());
//        float x = xy[0];
//        float y = xy[1];
//        float[] wh = worldToOpenGLMeasures(width, height);
//        float w = wh[0];
//        float h = wh[1];                
        
        float angle = MathUtils.radToDeg(info.getAngle());

        Matrix.setIdentityM(mMMatrix, 0);
        Matrix.translateM(mMMatrix, 0, -x, y, 0);
        Matrix.rotateM(mMMatrix, 0, angle, 0, 0, 1);
        Matrix.scaleM(mMMatrix, 0, w, h, 1.0f);

        Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, mMMatrix, 0);
        Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);

        GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);

        GLES20.glDrawElements(GLES20.GL_TRIANGLES, 6, GLES20.GL_UNSIGNED_SHORT,
                OpenGLUtils.getBoxTriangleIndexBuffer());
        checkGlError("glDrawElements");
    }

    @Override
    public void onSurfaceChanged(GL10 glUnused, int width, int height) {
        // Ignore the passed-in GL10 interface, and use the GLES20
        // class's static methods instead.
        GLES20.glViewport(0, 0, width, height);
        
        mWindowW = width;
        mWindowH = height;        
        mAspectRatio = (float) width / height;        
//        Matrix.frustumM(mProjMatrix, 0, -ratio, ratio, -1, 1, -1f, 1);
//        Matrix.orthoM(mProjMatrix, 0, -mAspectRatio, mAspectRatio, -1, 1, -1f, 1);
        
        Matrix.orthoM(mProjMatrix, 0, 0, 480, 0, 720, -1f, 1);
    }

    @Override
    public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
        GLES20.glDisable(GLES20.GL_DEPTH_TEST);
        GLES20.glDisable(GLES20.GL_CULL_FACE);
        GLES20.glDisable(GLES20.GL_DITHER);
        GLES20.glDisable(GLES20.GL_STENCIL_TEST);
        GLES20.glEnable(GLES20.GL_BLEND);
        GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
        
        // Set last tick
        lastTick = System.nanoTime();

        // Ignore the passed-in GL10 interface, and use the GLES20
        // class's static methods instead.
        mProgram = createProgram(mVertexShader, mFragmentShader);
        if (mProgram == 0) {
            return;
        }
        maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
        checkGlError("glGetAttribLocation aPosition");
        if (maPositionHandle == -1) {
            throw new RuntimeException(
                    "Could not get attrib location for aPosition");
        }
        maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
        checkGlError("glGetAttribLocation aTextureCoord");
        if (maTextureHandle == -1) {
            throw new RuntimeException(
                    "Could not get attrib location for aTextureCoord");
        }

        muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
        checkGlError("glGetUniformLocation uMVPMatrix");
        if (muMVPMatrixHandle == -1) {
            throw new RuntimeException(
                    "Could not get attrib location for uMVPMatrix");
        }

        /*
         * Create our textures. This has to be done each time the surface is
         * created.
         */
        int[] textures = new int[3];
        GLES20.glGenTextures(3, textures, 0);

        mBallTextureID = textures[0];
        mWallTextureID = textures[1];
        mPlayerTextureID = textures[2];
        OpenGLUtils.bindTextureImage(mBallTextureID,
                OpenGLUtils.loadBitmap(mContext, R.raw.ball2));
        OpenGLUtils.bindTextureImage(mWallTextureID,
                OpenGLUtils.loadBitmap(mContext, R.raw.wall));
        OpenGLUtils.bindTextureImage(mPlayerTextureID,
                OpenGLUtils.loadBitmap(mContext, R.raw.ball));

        Matrix.setLookAtM(mVMatrix, 0, 0, 0, -1f, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

        InitialazeBox2dWorld();
    }

    public void InitialazeBox2dWorld() {
        JniInterface.createWorld(world_minX, world_minY, world_maxX,
                world_maxY, gravityX, gravityY);
        
        float worldCentreX = (world_maxX - world_minX) / 2.0f;
        float worldCentreY = (world_maxY - world_minY) / 2.0f;
        
        // Walls
        int down = JniInterface.createBox(worldCentreX, world_minY + wallmW, world_width, wallW);
        downWall = new WallInfo(down, world_width, wallW);
        
        int up = JniInterface.createBox(worldCentreX, world_maxY - wallmW, world_width, wallW);
        upWall = new WallInfo(up, world_width, wallW);
        
        int right = JniInterface.createBox(world_minX + wallmW, worldCentreY, wallW, world_height);
        rightWall = new WallInfo(right, wallW, world_height);
        
        int left = JniInterface.createBox(world_maxX - wallmW, worldCentreY, wallW, world_height);
        leftWall = new WallInfo(left, wallW, world_height);
        
        // Corner walls
        int upLeft = JniInterface.createBox(world_minX + wallW, world_maxY - wallW, wallW, wallW*3);
        JniInterface.setRotate(upLeft, MathUtils.degToRad(-45.0f));
        upLeftCorner = new WallInfo(upLeft, wallW*3, wallW);

        int upRight = JniInterface.createBox(world_maxX - wallW, world_maxY - wallW, wallW, wallW*3);
        JniInterface.setRotate(upRight, MathUtils.degToRad(45.0f));
        upRightCorner = new WallInfo(upRight, wallW*3, wallW);

        int downLeft = JniInterface.createBox(world_minX + wallW, world_minY + wallW, wallW, wallW*3);
        JniInterface.setRotate(downLeft, MathUtils.degToRad(45.0f));
        downLeftCorner = new WallInfo(downLeft, wallW*3, wallW);

        int downRight = JniInterface.createBox(world_maxX - wallW, world_minY + wallW, wallW, wallW*3);
        JniInterface.setRotate(downRight, MathUtils.degToRad(-45.0f));
        downRightCorner = new WallInfo(downRight, wallW*3, wallW);

                
        playerIndex = JniInterface.createCirclePlayer(worldCentreX, 
                worldCentreY - world_height / 4f, blockWidth / 2f, 200f, 0f, 0f);
//        playerIndex = JniInterface.createBoxPlayer((world_maxX - world_minX) / 2f, 
//                (world_maxY - world_minY) / 2f, blockWidth, blockWidth, 50f, 0f, 0f);

        float r = ballRadius;
        firstIndex = lastIndex = JniInterface.createCircle(
                MathUtils.random(world_minX + wallW + r, world_maxX - wallW - r),
                MathUtils.random(world_minX + wallW + r, world_maxY - wallW - r), r,
                30f, 0.7f, 0f);
        JniInterface.setBodyAngularVelocity(firstIndex, 0.5f);
        JniInterface.setBodyLinearVelocity(lastIndex, 0, -20f);

        for (int i = 1; i < numBalls; i++) {
            lastIndex = JniInterface.createCircle(
                    MathUtils.random(world_minX + wallW + r, world_maxX - wallW - r),
                    MathUtils.random(world_minX + wallW + r, world_maxY - wallW - r),
                    r, 30f, 0.7f, 0f);
            JniInterface.setBodyAngularVelocity(lastIndex, 0.5f);
            JniInterface.setBodyLinearVelocity(lastIndex, 0, -20f);
        }

    }

    private int loadShader(int shaderType, String source) {
        int shader = GLES20.glCreateShader(shaderType);
        if (shader != 0) {
            GLES20.glShaderSource(shader, source);
            GLES20.glCompileShader(shader);
            int[] compiled = new int[1];
            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
            if (compiled[0] == 0) {
                Log.e(TAG, "Could not compile shader " + shaderType + ":");
                Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
                GLES20.glDeleteShader(shader);
                shader = 0;
            }
        }
        return shader;
    }

    private int createProgram(String vertexSource, String fragmentSource) {
        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
        if (vertexShader == 0) {
            return 0;
        }

        int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
        if (pixelShader == 0) {
            return 0;
        }

        int program = GLES20.glCreateProgram();
        if (program != 0) {
            GLES20.glAttachShader(program, vertexShader);
            checkGlError("glAttachShader");
            GLES20.glAttachShader(program, pixelShader);
            checkGlError("glAttachShader");
            GLES20.glLinkProgram(program);
            int[] linkStatus = new int[1];
            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
            if (linkStatus[0] != GLES20.GL_TRUE) {
                Log.e(TAG, "Could not link program: ");
                Log.e(TAG, GLES20.glGetProgramInfoLog(program));
                GLES20.glDeleteProgram(program);
                program = 0;
            }
        }
        return program;
    }

    private void checkGlError(String op) {
        int error;
        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
            Log.e(TAG, op + ": glError " + error);
            throw new RuntimeException(op + ": glError " + error);
        }
    }
        
    public void onKeyDown(int keyCode, KeyEvent event) {}
    
    public void actionDown() {
    }
    
    public void actionLeft() {
    }
    
    public void actionRight() {
    }
    
    public void actionUp() {
    }

    public void actionCenter() {}

    public boolean onTouchDown(float ex, float ey) {
        float[] xy = screenToWorld(ex, ey);
        float x = xy[0];
        float y = xy[1];
                
        JniInterface.mouseDown(x, y);
        
        return true;
    }

    public boolean onTouchUp(float ex, float ey) {
        float[] xy = screenToWorld(ex, ey);
        
        float x = xy[0];
        float y = xy[1];
        
        JniInterface.mouseUp(x, y);
        
        return true;
    }

    public boolean onTouchMove(float ex, float ey) {
        float[] xy = screenToWorld(ex, ey);
        
        float x = xy[0];
        float y = xy[1];
        
        JniInterface.mouseMove(x, y);
        
        return true;
    }
}
